在实际的开发中,我们也许会有这种需求:
公司的旗下有两个App,当客户已经登录一个App A的情况下,再登录另一个App B时,B不再需要繁琐的登录过程就可以直接使用A已经登录的信息。但是iOS系统下有这么一个安全机制:每个应用都有自己对应的沙盒,每个沙盒之间都是相互独立的,互不能访问(没有越狱的情况下)。
这种情况,我们应该怎么处理呢?

一、认识App Groups

AppGroup allows data sharing between two different apps or even app and widgets by creating one common shared path (like document directory). Data saved over there can be accessed by any app which is associated with that particular AppGroup. It is an offline data sharing between apps.

这是一段关于App groups的一段说明,告诉我们了App Groups可以使两个不同的APP进行数据共享,看起来这个是解决我们刚才那个问题的好方法。那就让我们开启我们的数据共享之旅吧!

二、App Groups 使用(推荐)

否则需要去控制台手动添加

image-20211125163024605

  1. 添加应用组:target -> Signing & Capabilities -> +Capability -> App Groups
  2. 如果不存在应用组,直接左下角添加;(若应用组为红色的,需要刷新同步创建应用组到控制台Apple Developer
  3. 如果存在直接添加即可(若groups只显示debug,也许需要分开添加合并

到这一步我们就可以愉快的开始应用间交互了~

轻量级的数据共享 使用UserDefaults

1
2
3
4
5
6
7
8
// 设置
let groupDefault = UserDefaults(suiteName: "自定义的App Group Id")
groupDefault?.set("测试结果", forKey: "groupKey")
groupDefault?.synchronize()

// 使用
let groupDefault = UserDefaults(suiteName: "自定义的App Group Id")
groupDefault?.value(forKey: "groupKey")

示例:

1
2
3
4
5
6
7
8
// 设置
let groupDefault = UserDefaults(suiteName: "group.io.agora.api.example.kilomind")
groupDefault?.set(channelName, forKey: "channelName")
groupDefault?.synchronize()

// 使用
let groupDefault = UserDefaults(suiteName: "group.io.agora.api.example.kilomind")
let channel = groupDefault?.value(forKey: "channelName") as? String

大量数据的共享,使用FileManager

1
2
3
4
5
6
7
8
// 设置
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "自定义的App Group Id")
let logsPath = containerURL!.appendingPathComponent("ShareGroup")

// 使用
let documentsDirectory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "自定义的App Group Id")
let replayPath = documentsDirectory?.appendingPathComponent("/ShareGroup")
let directoryContents = try! FileManager.default.contentsOfDirectory(at: replayPath!, includingPropertiesForKeys: nil, options: [])

示例:

1
2
3
4
5
6
7
8
9
// 设置
let groupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.io.agora.api.example.kilomind")!.appendingPathComponent("Library/Preferences")
let fileUrl = groupUrl.appendingPathComponent("appGroup.txt")
try! channelName.write(to: fileUrl, atomically: true, encoding: .utf8)

// 使用
let groupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.io.agora.api.example.kilomind")!.appendingPathComponent("Library/Preferences")
let fileUrl = groupUrl.appendingPathComponent("appGroup.txt")
let text = try? String(contentsOf: fileUrl, encoding: .utf8)

App Group在系统录屏的具体使用

添加扩展

target左下角添加,iOS -> Broadcast Upload Extension,bundleid设置为main bundleid.ScreenShare-Extension

使用

添加扩展后会在项目目录生成extension target,在该目录下 有入口文件SampleHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

import ReplayKit

class SampleHandler: RPBroadcastSampleHandler {

override func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?) {
let groupDefault = UserDefaults(suiteName: "group.io.agora.api.example.kilomind")
if let channel = groupDefault?.value(forKey: "channelName") as? String {
// share channel
} else {
// default
}
}

override func broadcastPaused() {
// User has requested to pause the broadcast. Samples will stop being delivered.
}

override func broadcastResumed() {
// User has requested to resume the broadcast. Samples delivery will resume.
}

override func broadcastFinished() {
// finished
}

override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
// send buffer
}
}

启动录制

  1. 获取系统录制按钮,点击即可弹出屏幕共享选择
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import ReplayKit

private var screenSharePicker = UIView()

func prepareBroadcaster() {
view.addSubview(screenSharePicker)
// screenSharePicker.frame = CGRect(x: 0, y:200, width: 60, height: 60)
if #available(iOS 12.0, *) {
let picker = RPSystemBroadcastPickerView(frame: .zero)
picker.frame = CGRect(x: 0, y:0, width: 60, height: 60)
picker.autoresizingMask = [.flexibleTopMargin, .flexibleRightMargin]
if let url = Bundle.main.url(forResource: "Agora-ScreenShare-Extension", withExtension: "appex", subdirectory: "PlugIns") {
if let bundle = Bundle(url: url) {
picker.preferredExtension = bundle.bundleIdentifier
}
}
screenSharePicker.addSubview(picker)
}else {
// Minimum support iOS version is 12.0
}
}

  1. 自定义UI方式触发弹出

方法一中的代码隐藏视图screenSharePicker.isHidden = true;自定义按钮事件触发屏幕共享。

1
2
3
4
5
6
7
@objc func click(_ btn: UIButton) {
for subview in screenSharePicker.subviews[0].subviews {
if let btn = subview as? UIButton {
btn.sendActions(for: .allTouchEvents)
}
}
}

结束录制

  1. 正常情况下结束会弹出一个错误结束框:
1
2
3
4
self.finishBroadcastWithError(NSError(domain: NSStringFromClass(SampleHandler.self), code: 0, userInfo: [NSLocalizedFailureReasonErrorKey: "屏幕共享已结束"]) as Error)

// 情况二,直接使用全局方法结束
finishBroadcastGracefully(self)
  1. 新建OC扩展文件BroadcastUploadHelpers,处理不弹框直接结束屏幕录制。参考Telegram
1
2
3
4
5
6
7
8
#ifndef BroadcastUploadHelpers_h
#define BroadcastUploadHelpers_h

#import <ReplayKit/ReplayKit.h>

void finishBroadcastGracefully(RPBroadcastSampleHandler * _Nonnull broadcastSampleHandler);

#endif /* BroadcastUploadHelpers_h */
1
2
3
4
5
6
7
8
#import "BroadcastUploadHelpers.h"

void finishBroadcastGracefully(RPBroadcastSampleHandler * _Nonnull broadcastSampleHandler) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
[broadcastSampleHandler finishBroadcastWithError:nil];
#pragma clang diagnostic pop
}